#pragma once

#include <string>
#include <sstream>
#include <stdexcept>
#include "RedisResult.h"
#include "RedisConnConfig.h"

namespace CwRedis {

    class RedisConn {

    public:

        RedisConn() = default;

        ~RedisConn() { if (conn_ != nullptr) redisFree(conn_); }

        /**
          * @brief  禁止对redis连接进行任何形式的拷贝或移动
          */
        RedisConn(const RedisConn &) = delete;

        RedisConn(RedisConn &&) = delete;

        RedisConn &operator=(const RedisConn &) = delete;

        /**
          * @brief  向指定redis数据库的ip和端口建立连接
          * @param  ip和端口
          * @retval 是否成功连接
          */
        bool connect(const std::string &ip, unsigned int port = 6379) { return connect(ip.c_str(), port); }

        bool connect(const char *, unsigned int port = 6379);

        /**
          * @brief  向指定redis数据库的信息建立连接
          * @param  RedisConnOption类
          * @retval 是否成功连接
          */
        bool connect(const RedisConnConfig &config);

        /**
          * @brief  获取错误信息
          * @note   如果还未建立连接将抛出std::runtime_error
          * @retval 错误信息字符串
          */
        std::string getError() const;

        /**
          * @brief  测试redis连接的连通性
          * @retval 是否连通
          */
        bool ping() { return conn_ != nullptr && !command("PING").isError(); }

        /**
          * @brief  执行一个redis命令
          * @note   如果未建立连接将抛出std::runtime_error异常
          * @param  要执行的命令，命令携带参数...
          * @retval RedisResult结果
          */
        template<typename T, typename ...Args>
        RedisResult command(T t, Args &&...args) {
            if (conn_ == nullptr) {
                throw std::runtime_error("empty ptr redis context");
            }
            std::stringstream stream;
            getCommand(stream, t, std::forward<Args>(args)...);
            return RedisResult((redisReply *) redisCommand(conn_, stream.str().c_str()));
        }

    private:

        template<typename T>
        void getCommand(std::stringstream &stream, T t) {
            stream << t;
        }

        template<typename T, typename... Args>
        void getCommand(std::stringstream &stream, T t, Args &&...args) {
            stream << t << ' ';
            getCommand(stream, std::forward<Args>(args)...);
        }

        redisContext *conn_ = nullptr;

    };

}
